package org.anderground.internal.graphics.impl;

import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Font;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.Shape;
import java.awt.Stroke;
import java.awt.geom.AffineTransform;
import java.awt.geom.Arc2D;
import java.awt.geom.Ellipse2D;
import java.awt.geom.FlatteningPathIterator;
import java.awt.geom.GeneralPath;
import java.awt.geom.Line2D;
import java.awt.geom.PathIterator;
import java.awt.geom.Rectangle2D;

import org.anderground.core.graphics.Canvas;
import org.anderground.core.graphics.Paint;
import org.anderground.core.graphics.RectF;
import org.anderground.internal.graphics.abstaractions.AbstractCanvas;
import org.anderground.internal.graphics.abstaractions.AbstractPaint;
import org.anderground.internal.graphics.abstaractions.AbstractPathDashPathEffect;
import org.anderground.internal.graphics.abstaractions.AbstractPaint.Cap;
import org.anderground.internal.graphics.abstaractions.AbstractPaint.Style;

public class AwtCanvas implements AbstractCanvas {

	private final Paint defaultPaint = new Paint();

	public static Canvas wrap(Graphics2D g) {
		return new Canvas(new AwtCanvas(g));
	}

	private Graphics2D graphics;

	public AwtCanvas(Graphics2D graphics) {
		this.graphics = graphics;
	}

	private void applyShader(AwtShader shader) {

		if (shader == null)
			return;

		if (shader instanceof AwtRadialGradient) {
			AwtRadialGradient radial = (AwtRadialGradient) shader;
			graphics.setColor(radial.getColorTo());
		}

		if (shader instanceof AwtLinearGradient) {
			AwtLinearGradient linear = (AwtLinearGradient) shader;
			graphics.setColor(linear.getColorTo());
		}

	}

	private void applyPathEffect(AwtPathEffect effect) {

		if (effect == null)
			return;

		if (effect instanceof AwtPathDashPathEffect) {
			AwtPathDashPathEffect dashEffect = (AwtPathDashPathEffect) effect;

			AbstractPathDashPathEffect.Style dashStyle = dashEffect.getStyle();
			GeneralPath path = dashEffect.getPath();
			float advance = dashEffect.getAdvance();
			float phase = dashEffect.getPhase();
			if (path != null) {
				Stroke stroke = new ConventionalizedStroke(path, advance,
						phase, dashStyle);
				graphics.setStroke(stroke);
			}
		}

	}

	private void applyPaint(AbstractPaint paint) {

		if (paint == null)
			paint = new AwtPaint();

		graphics.setColor(new Color(paint.getColor(), true));
		graphics.setStroke(new ConventionalizedStroke(paint.getStrokeWidth(),
				paint.getStrokeCap()));

		if (paint.getPathEffect() != null) {
			applyPathEffect((AwtPathEffect) paint.getPathEffect().getDelegate());
		}

		if (paint.getShader() != null) {
			applyShader((AwtShader) paint.getShader().getDelegate());
		}

		graphics.addRenderingHints(new RenderingHints(
				RenderingHints.KEY_ANTIALIASING, AwtPaint.castAntiAlias(paint
						.isAntiAlias())));
		// Font can be changed in Paint.
		graphics.setFont(new Font(AwtPaint.FONT_NAME, AwtPaint.FONT_STYLE,
				(int) paint.getTextSize()));
	}

	public void put(Shape shape, Style style) {
		if (style == Style.STROKE) {
			graphics.draw(shape);
		} else if (style == Style.STROKE) {
			graphics.fill(shape);
		} else {
			graphics.fill(shape);
			graphics.draw(shape);
		}
	}

	@Override
	public void drawArc(RectF oval, float startAngle, float sweepAngle,
			Paint paint) {
		applyPaint(paint);

		Shape shape = new Arc2D.Float(oval.getLeft(), oval.getTop(), oval
				.width(), oval.height(), -startAngle, -sweepAngle, Arc2D.PIE);

		put(shape, paint.getStyle());
		applyPaint(defaultPaint);
	}

	@Override
	public void drawCircle(float cx, float cy, float radius, Paint paint) {
		applyPaint(paint);

		Shape shape = new Ellipse2D.Float(cx - radius, cy - radius, radius * 2,
				radius * 2);

		put(shape, paint.getStyle());

		applyPaint(defaultPaint);
	}

	@Override
	public void drawLine(float startX, float startY, float stopX, float stopY,
			Paint paint) {
		applyPaint(paint);

		Line2D line = new Line2D.Float(startX, startY, stopX, stopY);
		put(line, AbstractPaint.Style.STROKE);

		applyPaint(defaultPaint);
	}

	@Override
	public void drawPoint(float x, float y, Paint paint) {
		applyPaint(paint);

		Shape point = new Line2D.Float(x, y, x, y);

		put(point, AbstractPaint.Style.FILL_AND_STROKE);
		applyPaint(defaultPaint);
	}

	@Override
	public void drawText(String text, float x, float y, Paint paint) {
		applyPaint(paint);

		graphics.drawString(text, x, y);

		applyPaint(defaultPaint);
	}

	@Override
	public void translate(float dx, float dy) {
		graphics.translate(dx, dy);
	}

	@Override
	public void scale(float sx, float sy) {
		graphics.scale(sx, sy);
	}

	private class ConventionalizedStroke implements Stroke {

		private AffineTransform t = new AffineTransform();
		private BasicStroke defaultStroke;

		AbstractPathDashPathEffect.Style style;

		private Shape shape = null;

		private float advance;
		private float phase;

		public ConventionalizedStroke() {
			defaultStroke = new BasicStroke();
		}

		public ConventionalizedStroke(float width, Cap cap) {
			initDefaultStroke(width, cap);
		}

		public ConventionalizedStroke(GeneralPath path, float advance,
				float phase, AbstractPathDashPathEffect.Style style) {
			initShapedStroke(path, advance, phase, style);
		}

		public ConventionalizedStroke(float width, Cap cap, GeneralPath path,
				float advance, float phase,
				AbstractPathDashPathEffect.Style style) {
			initDefaultStroke(width, cap);
			if (path != null) {
				initShapedStroke(path, advance, phase, style);
			}
		}

		private void initDefaultStroke(float width, Cap cap) {
			defaultStroke = new BasicStroke(width, AwtPaint.castCap(cap),
					BasicStroke.JOIN_MITER);
		}

		private void initShapedStroke(GeneralPath path, float advance,
				float phase, AbstractPathDashPathEffect.Style style) {
			Rectangle2D bounds = path.getBounds2D();
			t.setToTranslation(-bounds.getCenterX(), -bounds.getCenterY());
			this.shape = t.createTransformedShape(path);
			this.advance = advance;
			this.phase = phase;
			this.style = style;
		}

		private Shape dashStrokedShape(Shape p) {
			GeneralPath result = new GeneralPath();
			PathIterator it = new FlatteningPathIterator(p
					.getPathIterator(null), 1);

			float thisX = 0, thisY = 0;
			float lastX = 0, lastY = 0;

			float points[] = new float[6];
			float next = phase;
			int type = 0;

			while (!it.isDone()) {
				type = it.currentSegment(points);
				switch (type) {
				case PathIterator.SEG_MOVETO:
					lastX = points[0];
					lastY = points[1];
					if (style == AbstractPathDashPathEffect.Style.TRANSLATE) {
						result.moveTo(lastX, lastY);
					} else if (style == AbstractPathDashPathEffect.Style.MORPH) {
						result.lineTo(lastX, lastY);
					}
					break;
				case PathIterator.SEG_LINETO:
					thisX = points[0];
					thisY = points[1];
					float dx = thisX - lastX;
					float dy = thisY - lastY;
					float distance = (float) Math.sqrt(dx * dx + dy * dy);
					if (distance >= next) {
						float theta = (float) Math.atan2(dy, dx);
						float r = 1.0f / distance;
						while (distance >= next) {
							float putX = lastX + next * dx * r;
							float putY = lastY + next * dy * r;
							t.setToTranslation(putX, putY);
							if (style == AbstractPathDashPathEffect.Style.ROTATE) {
								t.rotate(theta);
							}
							result.append(t.createTransformedShape(shape),
									false);
							next += advance;
						}
					}
					next -= distance;
					lastX = thisX;
					lastY = thisY;
					break;
				}

				it.next();
			}

			return result;
		}

		@Override
		public Shape createStrokedShape(Shape arg0) {

			if (shape != null) {
				return dashStrokedShape(arg0);
			}
			return defaultStroke.createStrokedShape(arg0);
		}

	}

}
